import ctypes
import os
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QRadioButton, QSpinBox, QTextBrowser, QLineEdit, \
    QCheckBox, QDoubleSpinBox, QComboBox, QFileDialog
from PyQt5 import uic, QtCore
import sys
from selenium.webdriver.chrome.options import Options
from selenium import webdriver

from domains.Assistant import Assistant
from domains.ConfigParser import ConfigParser
from domains.QuestionParser import QuestionParser
from utils.lg import logger

ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")  # 设置任务栏图标需要添加的内容


class Settings(QWidget):
    def __init__(self, config_parser):
        super().__init__()
        self.config_parser = config_parser

        self.ui = uic.loadUi(r"gui/Settings.ui")
        self.user_name_edit: QLineEdit = self.ui.user_name_edit
        self.psw_edit: QLineEdit = self.ui.psw_edit
        self.is_headless_box: QCheckBox = self.ui.is_headless_box
        self.is_max_size_box: QCheckBox = self.ui.is_max_size_box
        self.loading_delay_spin: QDoubleSpinBox = self.ui.loading_delay_spin
        self.answering_delay_spin: QDoubleSpinBox = self.ui.answering_delay_spin
        self.finished_delay_spin: QDoubleSpinBox = self.ui.finished_delay_spin
        self.log_level_box: QComboBox = self.ui.log_level_box
        self.log_rotation_spin: QSpinBox = self.ui.log_rotation_spin
        self.confirm_btn: QPushButton = self.ui.confirm_btn
        self.is_show_psw_btn: QPushButton = self.ui.is_show_psw_btn

        self.confirm_btn.clicked.connect(self.confirm_btn_clicked)
        self.is_show_psw_btn.clicked.connect(self.is_show_psw_btn_clicked)

    def is_show_psw_btn_clicked(self):
        """显示/隐藏密码"""
        if self.psw_edit.echoMode() == 2:
            self.psw_edit.setEchoMode(0)
        else:
            self.psw_edit.setEchoMode(2)
        # print(self.psw_edit.echoMode())  # Password模式下打印 2

    def confirm_btn_clicked(self):
        """点击确认按钮关闭配置页面"""
        # 保存配置
        self.save_attrs()
        self.ui.close()

    def save_attrs(self):
        attrs_dict = {
            'user_name': self.user_name_edit.text(),
            'user_psw': self.psw_edit.text(),
            'is_max_size': self.is_max_size_box.isChecked(),
            'is_headless': self.is_headless_box.isChecked(),
            'loading_delay': float(self.loading_delay_spin.text()),
            'answering_delay': float(self.answering_delay_spin.text()),
            'finished_delay': float(self.finished_delay_spin.text()),
            'log_level': self.log_level_box.currentText(),
            'log_rotation': int(self.log_rotation_spin.text()),
            'is_show_psw': self.psw_edit.echoMode() == 2,
        }
        self.config_parser.write(**attrs_dict)


class MyThread(QThread):
    finished_signal = pyqtSignal()

    def __init__(self, driver, config_parser, file_path, times=1):
        super().__init__()
        self.driver = driver
        self.config_parser = config_parser
        self.file_path = file_path
        self.assistant = None
        self.que_parser = None
        self.times = times

    def run(self):
        if self.file_path.endswith(".html"):
            # 单试题模式
            self.que_parser = QuestionParser(self.file_path)
            self.assistant = Assistant(self.driver, self.config_parser, self.que_parser)
            self.assistant.run(self.times)
        else:
            # 多试题模式
            files = os.listdir(self.file_path)
            que_parser_list = []
            for file in files:
                if file.endswith(".html"):
                    que_parser_list.append(QuestionParser(os.path.join(self.file_path, file)))
            self.assistant = Assistant(self.driver, self.config_parser, que_parser_list)
            self.assistant.run(self.times)
        self.driver.close()
        self.finished_signal.emit()

    def stop(self):
        self.driver.stop_client()
        # 需要用quit而不是close，否则不会杀死chromedriver进程，用几次程序就卡住了
        self.driver.quit()


class LryAssistant(QWidget):
    def __init__(self):
        super().__init__()
        # 设置ui，定义组件名称
        self.ui = uic.loadUi("gui/LryAssistant.ui")
        self.setting_btn: QPushButton = self.ui.setting_btn
        self.single_mode_btn: QRadioButton = self.ui.radioButton
        self.multi_mode_btn: QRadioButton = self.ui.radioButton_2
        self.times_spin: QSpinBox = self.ui.times_spin
        self.choose_file_btn: QPushButton = self.ui.choose_file_btn
        self.start_btn: QPushButton = self.ui.start_btn
        self.stop_btn: QPushButton = self.ui.stop_btn
        self.browser: QTextBrowser = self.ui.browser

        # 获取配置器并获取参数
        self.config_parser = ConfigParser("configs.json")
        # 得到配置首选项窗口对象
        self.Settings = Settings(self.config_parser)
        # 将参数同步到图形化界面
        self.set_attrs()
        # 初始化打印日志
        self.set_logger()

        # driver解析器
        self.driver = None

        # QuestionParser的解析路径
        self.file_path = ""

        # 运行的线程
        self.thread = MyThread(self.driver, self.config_parser, self.file_path)

        # 设置提示
        self.browser.setText(
            "请选择一个html文件或包含html文件的文件夹，后者将执行文件夹内的所有html文件\n(首次执行程序可能需要加载一段时间，请耐心等待)"
        )

        # 点击设置按钮打开Settings窗口
        self.setting_btn.clicked.connect(self.setting_btn_clicked)
        self.start_btn.setEnabled(False)  # 设置按钮初始不可点击
        self.stop_btn.setEnabled(False)  # 设置按钮初始不可点击
        self.single_mode_btn.setChecked(True)  # 设置默认使用单份试题模式

        # 选择文件按钮的槽函数连接
        self.choose_file_btn.clicked.connect(self.choose_file_btn_clicked)

        # 换了模式后就不给执行，怕出现选了单试题模式然后执行前又换了多试题模式的情况
        self.single_mode_btn.toggled.connect(self.mode_btn_toggled)
        self.multi_mode_btn.toggled.connect(self.mode_btn_toggled)

        # 执行
        self.start_btn.clicked.connect(self.start_btn_clicked)
        # 线程停止信号
        self.stop_btn.clicked.connect(self.stop_btn_clicked)

    def set_chrome_driver(self):
        options = Options()
        # 去掉自动化测试的提示
        options.add_experimental_option('useAutomationExtension', False)
        options.add_experimental_option("excludeSwitches", ['enable-automation'])
        # 取消保存密码提示框
        prefs = {"credentials_enable_service": False,
                 "profile.password_manager_enabled": False
                 }
        options.add_experimental_option("prefs", prefs)
        # 设置无头模式
        if self.config_parser.configs["is_headless"]:
            options.add_argument('--headless')
        # 获取driver对象
        self.driver = webdriver.Chrome(options)

    def set_logger(self):
        """初始化打印日志"""
        logger.remove()  # 去除现在所有的logger输出
        # 设置日志的文件输出
        logger.add(r'logs\ass_log.log', level="DEBUG",
                   rotation=str(self.config_parser.configs["log_rotation"]) + " MB", encoding="utf-8",
                   )
        logger.add(sys.stdout, level="DEBUG")

        def print_to_browser_sink(message):
            """自定义日志sink函数"""
            self.browser.append(message.format())
            self.browser.moveCursor(self.browser.textCursor().End)

        logger.add(print_to_browser_sink, level=self.config_parser.configs["log_level"],
                   format="{time:MM/DD HH:mm:ss} | {level} | {message}")

    def mode_btn_toggled(self):
        """模式选择变化后取消执行功能"""
        self.start_btn.setEnabled(False)

    def choose_file_btn_clicked(self):
        """选择文件或者文件夹"""
        if self.single_mode_btn.isChecked():
            # 选择了单份试题模式
            # 打开单个文件
            # self.browser.setText("single")
            file_path, _ = QFileDialog.getOpenFileName(self, "选择一个文件", "./", "html文件(*.html)")
            if file_path:
                self.file_path = file_path
                file_name = file_path.rsplit("/")[-1]
                self.browser.setText(f"选择文件: {file_name}\n路径: {file_path}")
                # 选择文件之后才能够执行
                self.start_btn.setEnabled(True)
        else:
            # 选择了多份实体模式
            # 打开以一个文件夹
            # self.browser.setText("multi")
            file_path = QFileDialog.getExistingDirectory(self, "选择一个文件夹", "./")
            if file_path:
                self.file_path = file_path
                file_name = file_path.rsplit("/")[-1]
                self.browser.setText(f"选择文件夹: {file_name}\n路径: {file_path}\n文件夹内容: {os.listdir(file_path)}")
                # 选择文件夹之后才能够执行
                self.start_btn.setEnabled(True)

    def setting_btn_clicked(self):
        """打开设置首选项页面"""
        self.Settings.ui.show()

    def start_btn_clicked(self):
        """执行答题"""
        # start按钮连续设置可用和不可用可能会直接崩溃(指选好文件立刻点执行)
        self.browser.setText("程序即将开始运行，首次运行可能需要加载一段时间，请耐心等待")
        self.browser.moveCursor(self.browser.textCursor().End)
        # 先将按钮设置为不可用
        self.start_btn.setEnabled(False)
        # logger重新设置
        self.set_logger()
        # driver重新设置
        self.set_chrome_driver()
        # 获取循环次数
        times = int(self.times_spin.text())
        # 启动线程执行
        self.thread = MyThread(self.driver, self.config_parser, self.file_path, times)
        # 线程结束信号(每个线程都要单独绑定)
        self.thread.finished_signal.connect(self.start_btn_finished)
        # 执行
        self.thread.start()
        # 开启停止按钮
        self.stop_btn.setEnabled(True)

    def start_btn_finished(self):
        # 执行完成后再将按钮设置为可用
        self.start_btn.setEnabled(True)
        # 执行完成后将取消按钮设置为不可用
        self.stop_btn.setEnabled(False)
        self.browser.append("-" * 25 + "全部试题已经完成" + "-" * 25)
        self.browser.moveCursor(self.browser.textCursor().End)

    def stop_btn_clicked(self):
        # 打断线程
        self.thread.terminate()
        self.thread.stop()
        self.stop_btn.setEnabled(False)
        self.browser.append("-" * 25 + "已经停止执行" + "-" * 25)
        self.browser.moveCursor(self.browser.textCursor().End)

    def set_attrs(self):
        self.Settings.user_name_edit.setText(self.config_parser.configs["user_name"])
        self.Settings.psw_edit.setText(self.config_parser.configs["user_psw"])
        self.check_box_judge(self.Settings.is_max_size_box, "is_max_size")
        self.check_box_judge(self.Settings.is_headless_box, "is_headless")
        self.Settings.loading_delay_spin.setValue(self.config_parser.configs["loading_delay"])
        self.Settings.answering_delay_spin.setValue(self.config_parser.configs["answering_delay"])
        self.Settings.finished_delay_spin.setValue(self.config_parser.configs["finished_delay"])
        self.Settings.log_level_box.setCurrentText(self.config_parser.configs["log_level"])
        self.Settings.log_rotation_spin.setValue(self.config_parser.configs["log_rotation"])

    def check_box_judge(self, check_box: QCheckBox, name: str):
        if check_box.isChecked():
            # 配置文件不点击
            if not self.config_parser.configs[name]:
                check_box.click()
        else:
            # 配置文件要点击
            if self.config_parser.configs[name]:
                check_box.click()


if __name__ == '__main__':
    # 适配高分辨率屏幕
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)

    app = QApplication(sys.argv)
    widget = LryAssistant()
    widget.ui.show()
    sys.exit(app.exec_())
